home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / altchars < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  40.7 KB  |  1,116 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) altchars.gawk 1.2 96/11/30
  3. # 96/05/21 john h. dubois iii
  4. # 96/05/29 Added all options.
  5. # 96/08/06 Added ib options.
  6. # 96/11/30 Moved altInit code into a function
  7. BEGIN {
  8.     Name = "altchars"
  9.     Usage = "Usage: " Name " [-ahobi] [-t<termtype>]"
  10.     ARGC = Opts(Name,Usage,"abhiot:",0)
  11.     if ("h" in Options) {
  12.     printf \
  13. "%s: print alternate character set for a terminal using terminfo database.\n"\
  14. "%s\n"\
  15. "%s prints the characters that are rendered in the alternate character set\n"\
  16. "by terminfo-based programs that need to use the graphic (non-ASCII)\n"\
  17. "characters that are recorded in the terminfo \"acsc\" capability.  Before\n"\
  18. "printing the characters, these programs send a sequence that switches the\n"\
  19. "terminal into the alternate set.  By default, %s does not send the\n"\
  20. "character set switching sequence, so the characters it prints appear as\n"\
  21. "they do in the acsc string.\n"\
  22. "The 'Tag' column gives the VT100-equivalent character used in the acsc\n"\
  23. "capability to tell what the function of each character is.\n"\
  24. "The 'Pos' column gives the position of each character in the acsc string.\n"\
  25. "The 'Sym' column is a symbolic representation of the character.\n"\
  26. "acsc is used mainly to get the characters used to draw boxes.\n"\
  27. "Options:\n"\
  28. "-h: Print this help.\n"\
  29. "-a: Print an extra 'Char' column with each alternate character printed with\n"\
  30. "    the alternate character set turned on, so that they will show up as the\n"\
  31. "    real graphic characters if the terminal interprets them correctly.\n"\
  32. "-b: Draw a partitioned box using the alternate character set, with the\n"\
  33. "    characters also shown as they appear in the standard character set,\n"\
  34. "    followed the acsc tag characters.\n"\
  35. "-i: Emit a channel map that maps some characters of the IBM graphic\n"\
  36. "    character set into alternate character set sequences for the terminal\n"\
  37. "    type that %s operates on.  The characters mapped are those that the\n"\
  38. "    acsc capability defines.  If this map is installed with mapchan, an\n"\
  39. "    application that uses those IBM graphic characters will be able to\n"\
  40. "    display them on the terminal.  The mapchan facility does not have\n"\
  41. "    enough room to store much output mapping, so only the box drawing\n"\
  42. "    characters and a few others are mapped.\n"\
  43. "-t<termtype>: Print the alternate characters for the given terminal type,\n"\
  44. "    instead of the terminal type given by the environment variable TERM.\n"\
  45. "-o: Print all non-printing characters in octal.  The default is to print\n"\
  46. "    characters above ASCII value 127 in octal, and all other non-printing\n"\
  47. "    characters in the symbolic form ^X.\n",
  48.     Name,Usage,Name,Name,Name
  49.     exit 0
  50.     }
  51.     if (ARGC > 1) {
  52.     print Name ": invalid argument.  Use -h for help." > "/dev/stderr"
  53.     exit 1
  54.     }
  55.     if ((Err = ExclusiveOptions("a,b,i,o",Options)) != "") {
  56.     printf "Error: %s\n",Err > "/dev/stderr"
  57.     Err = 1
  58.     exit(1)
  59.     }
  60.     if ("t" in Options)
  61.     term = Options["t"]
  62.     else
  63.     term = ENVIRON["TERM"]
  64.     Octal = "o" in Options
  65.     drawBox = "b" in Options
  66.     AltChars = "a" in Options
  67.     chanMap = "i" in Options
  68.     list = !(drawBox || chanMap)
  69.     Assign(CharNames,
  70.     "+=arrow pointing right|,=arrow pointing left|.=arrow pointing down|"\
  71.     "0=solid square block|I=lantern symbol|-=arrow pointing up|`=diamond|"\
  72.     "a=checker board|f=degree symbol|g=plus/minus|h=board of squares|"\
  73.     "j=lower right corner|k=upper right corner|l=upper left corner|"\
  74.     "m=lower left corner|n=plus|o=scan line 1|q=horizontal line|"\
  75.     "s=scan line 9|t=left tee|u=right tee|v=bottom tee|w=top tee|"\
  76.     "x=vertical line|~=bullet", "|","=")
  77.     CopySet(CharNames,vtchars)
  78.  
  79.     if (ret = altInit(tinfo,term,0,AltMap,num))
  80.     exit ret
  81.     caplist = "enacs,smacs,rmacs"
  82.     split(caplist,capnames,",")
  83.     split("Enable,Start,End",capdesc,",")
  84.     for (i = 1; i in capnames; i++) {
  85.     capname = capnames[i]
  86.     if (list && capname in tinfo)
  87.         printf "%s alternate character set sequence: %s\n",capdesc[i],
  88.         Uncontrol(tinfo[capname],Octal)
  89.     }
  90.     acsc = tinfo["acsc"]
  91.     # If we will be printing chars in alternate character set,
  92.     # get start/end alt char set sequences and enable alternate character set.
  93.     if (AltChars || drawBox || chanMap) {
  94.     if ("smacs" in tinfo)
  95.         smacs = tinfo["smacs"]
  96.     if ("rmacs" in tinfo)
  97.         rmacs = tinfo["rmacs"]
  98.     if ("enacs" in tinfo)
  99.         printf "%s",tinfo["enacs"]
  100.     if (AltChars)
  101.         printf "Char "
  102.     }
  103.     if (drawBox) {
  104.     Format = "%-5s   %-5s   %s\n"
  105.     AltMap[" "] = " "
  106.     space = "   "
  107.     printf Format,"Alt","Chars","Tags"
  108.     split("lqwqk,x x x,tqnqu,x x x,mqvqj",elem,",")
  109.     for (i = 1; i <= 5; i++) {
  110.         s = elem[i]
  111.         mapped = graphic = ""
  112.         for (j = 1; (c = substr(s,j,1)) != ""; j++) {
  113.         a = AltMap[c]
  114.         mapped = mapped a
  115.         if (c == " ")
  116.             # Some terminals do not have space as space in alt charset
  117.             graphic = graphic rmacs " " smacs
  118.         else
  119.             graphic = graphic a
  120.         }
  121.         printf Format,smacs graphic rmacs,mapped,s
  122.     }
  123.     exit 0
  124.     }
  125.     if (chanMap) {
  126.     toMapChan(S)
  127.     MakeMapchanTable()
  128.     #ibmset = "0\333a\261g\361h\262j\331k\277l\332m\300n\305q\304t"\
  129.     #"\303u\264v\301w\302x\263+\032.\031-\030`\004~\011"
  130.     #ibmset = "j\331k\277l\332m\300n\305q\304t\303u\264v\301w\302x\263"
  131.     ibmset = "j\331k\277l\332m\300n\305q\304t\303u\264v\301w\302x\263"\
  132.     "~\372a\260"
  133.  
  134.     len = length(ibmset)
  135.     for (i = 1; i < len; i += 2)
  136.         ibmMap[substr(ibmset,i,1)] = substr(ibmset,i+1,1)
  137.     print "input\n\noutput\n"
  138.     for (c in AltMap)
  139.         if (c in ibmMap) {
  140.         printf "# %s\n",CharNames[c]
  141.         printf "%s %s\n",toMapChan(ibmMap[c]),
  142.         toMapChan(smacs AltMap[c] rmacs)
  143.         }
  144.     exit 0
  145.     }
  146.     Format = "%-3s %3s %-5s %s\n"
  147.     printf Format,"Tag","Pos","Sym","Description"
  148.     for (j = 1; j in num; j++) {
  149.     vtchar = num[j]
  150.     altchar = AltMap[vtchar]
  151.     if (vtchar in CharNames) {
  152.         if (AltChars)
  153.         printf "%s",smacs altchar rmacs "    "
  154.         printf Format,vtchar,j,
  155.         Uncontrol(altchar,Octal),CharNames[vtchar]
  156.         delete vtchars[vtchar]
  157.     }
  158.     else
  159.         unknown[vtchar] = altchar
  160.     }
  161.     if (!(IsEmpty(unknown))) {
  162.     print \
  163.     "Unknown line drawing character label(s) found in acsc capability:" \
  164.     > "/dev/stderr"
  165.     for (char in unknown)
  166.         u = u char
  167.     print Uncontrol(u,Octal) > "/dev/stderr"
  168.     }
  169.     if (!(IsEmpty(vtchars))) {
  170.     print "Unassigned characters from the alternate character set:" \
  171.     > "/dev/stderr"
  172.     for (char in vtchars)
  173.         printf "%s   %s\n",char,CharNames[char] > "/dev/stderr"
  174.     }
  175. }
  176.  
  177. function toMapChan(S,  i,len,Output) {
  178.     len = length(S)
  179.     Output = ""
  180.     for (i = 1; i <= len; i++) {
  181.     Output = Output " " mchar2octal[substr(S,i,1)]
  182.     }
  183.     return substr(Output,2)
  184. }
  185.  
  186. function MakeMapchanTable(  i,c) {
  187.     for (i = 1; i < 32; i++)
  188.     mchar2octal[sprintf("%c",i)] = sprintf("%3d",i)
  189.     for (i = 32; i < 127; i++)
  190.     mchar2octal[sprintf("%c",i)]  = sprintf("'%c'",i)
  191.     mchar2octal[sprintf("%c",127)] = 127
  192.     for (i = 128; i < 256; i++)
  193.     mchar2octal[sprintf("%c",i)] = sprintf("%3d",i)
  194. }
  195.  
  196. # altInit(): Get alternate character set terminfo capabilities.
  197. # term, noerror: see tiget().
  198. # tinfo: contains the acsc capability, and any of the enacs, smacs, and rmacs
  199. # capabilities that are defined for the terminal.  Each is indexed by its
  200. # capability name.  enacs is used to enable the alternate character set;
  201. # smacs starts it; rmacs ends it.  acsc is the mapping of vt100 alternate
  202. # character codes to those appropriate for the given terminal.
  203. # AltMap is the acsc string broken down with each alternate character indexed
  204. # by its vt100 equivalent.  num is an ordered list of the vt100 characters
  205. # indexed starting with 1, for applications that need to know what order they
  206. # were given in.
  207. # The alternate characters and their indexes (vt100 equivalents) are:
  208. # 0  solid square block        a  checker board    f  degree symbol
  209. # g  plus/minus            h  board of squares    j  lower right corner
  210. # k  upper right corner        l  upper left corner    m  lower left corner
  211. # n  plus            q  horizontal line    t  left tee
  212. # u  right tee            v  bottom tee        w  top tee
  213. # x  vertical line        +  arrow pointing right    .  arrow pointing down
  214. # -  arrow pointing up        ,  arrow pointing left    `  diamond
  215. # ~  bullet            I  lantern symbol    o  scan line 1
  216. # s  scan line 9
  217. function altInit(tinfo,term,noerror,AltMap,num,  ret,caplist,acsc,len,j,i) {
  218.     if (ret = tiget("acsc",tinfo,term)) {
  219.     # All other types of errors cause tput to print an informative message
  220.     # to stderr, which is not redirected.
  221.     if (!noerror && ret == 1)
  222.         printf "Terminal type %s has no acsc capability.\n",
  223.         term > "/dev/stderr"
  224.     return ret
  225.     }
  226.     caplist = "enacs,smacs,rmacs"
  227.     tiget(caplist,tinfo,term)
  228.     acsc = tinfo["acsc"]
  229.     len = length(acsc)
  230.     j = 0
  231.     for (i = 1; i < len; i += 2)
  232.     AltMap[num[++j] = substr(acsc,i,1)] = substr(acsc,i+1,1)
  233. }
  234.  
  235. # tiget: get terminfo capabilities.
  236. # capnames is a comma-separated list of terminfo capabilities to get.
  237. # Each capability is put in tinfo[], indexed by capability name.
  238. # If term is passed, it is the terminal type to get the capabilities for.
  239. # If not, the value of the environment variable TERM is used.
  240. # If noerror is true, error messages are suppressed.
  241. # Return value: the exit status of the last tput.
  242. function tiget(capnames,tinfo,term,noerror,  cmd,RS,ret,names,capname,i) {
  243.     if (term == "")
  244.     term = ENVIRON["TERM"]
  245.     split(capnames,names,",")
  246.     RS = ""    # this makes the record separator be "\n\n", which hopefully
  247.         # is not very common in terminfo capabilities
  248.     for (i = 1; i in names; i++) {
  249.     capname = names[i]
  250.     cmd = "exec tput -T " term " " capname
  251.     if (noerror)
  252.         cmd = cmd " 2>/dev/null"
  253.     cmd | getline
  254.     if (!(ret = close(cmd)))
  255.         # printf interprets many of the escape chars in the same manner that
  256.         # the terminfo library does... not perfect, but better than nothing
  257.         tinfo[capname] = sprintf($0)
  258.     }
  259.     return ret
  260. }
  261.  
  262. ### Begin array routines
  263.  
  264. # InitArr: Initialize an array with values.
  265. # Ind and Vals are separated into lists on Sep.
  266. # For each item in Ind, an index with that name is created in Arr[],
  267. # and the value with the same position in Vals is stored in it.
  268. # Global variables: none.
  269. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  270.     split(Ind,indnames,sep)
  271.     split(Vals,values,sep)
  272.     for (numind in indnames)
  273.     Arr[indnames[numind]] = values[numind]
  274. }
  275.  
  276. function ClearArr(Arr,  Elem) {
  277.     for (Elem in Arr)
  278.     delete Arr[Elem]
  279. }
  280. # Subtract the values in Subtrahend from those in Minuend
  281. function SubtractArr(Minuend,Subtrahend,  Elem) {
  282.     for (Elem in Subtrahend)
  283.     Minuend[Elem] -= Subtrahend[Elem]
  284. }
  285. # For each element of the array In, an element is created in Out having
  286. # an index equal to the value of the element in In and a value equal to 
  287. # the index of the element in In.
  288. function Invert(In,Out,  Index) {
  289.     for (Index in In)
  290.     Out[In[Index]] = Index
  291. }
  292.  
  293. # Assign: make an array from a list of assignments.
  294. # An index with the name of each variable in the list is created in the array.
  295. # Its value is set to the value given for it.
  296. # Input variables: 
  297. # Elements is a string containing the list of variable-value pairs.
  298. # Sep is the string that separates the pairs in the list.
  299. # AssignOp is the string that separates variables from values.
  300. # Output variables:
  301. # Arr is the array.
  302. # Return value: the number of elements added to the set.
  303. # Example:
  304. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  305. function Assign(Arr,Elements,Sep,AssignOp,
  306. Num,Names,Elem,Assignments,Assignment,i) {
  307.     Num = split(Elements,Assignments,Sep)
  308.     for (i = 1; i <= Num; i++) {
  309.     Assignment = Assignments[i]
  310.     Ind = index(Assignment,AssignOp)
  311.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  312.     }
  313.     return Num
  314. }
  315.  
  316. ### End array routines
  317. ### Begin UnControl routines
  318.  
  319. # @(#) uncontrol.awk 1.1 96/05/29
  320. # 92/11/09 john h. dubois iii (john@armory.com)
  321. # 96/05/29 Added octal-only conversion.
  322.  
  323. # Uncontrol(S): Convert control characters in S to symbolic form.
  324. # Characters in S with values < 32 and with value 127 are converted to the form
  325. # ^X.  Characters with value >= 128 are converted to the octal form \0nnn,
  326. # where nnn is the octal value of the character.
  327. # The resulting string is returned.
  328. # If OctalOnly is true, octal numbers are used for all symbolic values instead
  329. # of ^X.
  330. # Global variables: UncTable[] and char2octal[].
  331. function Uncontrol(S,OctalOnly,  i,len,Output) {
  332.     len = length(S)
  333.     Output = ""
  334.     if (!("a" in UncTable))
  335.     MakeUncontrolTable()
  336.     for (i = 1; i <= len; i++)
  337.     Output = Output \
  338.     (OctalOnly ? char2octal[substr(S,i,1)] : UncTable[substr(S,i,1)])
  339.     return Output
  340. }
  341.  
  342. # MakeUncontrolTable: Make tables for use by Uncontrol().
  343. # Global variables:
  344. # UncTable[] is made into a character -> symbolic character lookup table
  345. # with characters with values < 32 and with value 127 converted to the form
  346. # ^X, and characters with value >= 128 are converted to the octal form \0nnn.
  347. # char2octal[] is made into a similar table but with all non-printing chars
  348. # in the form \0nnn.
  349. function MakeUncontrolTable(  i,c) {
  350.     for (i = 0; i < 32; i++) {
  351.     UncTable[c = sprintf("%c",i)] = "^" sprintf("%c",i + 64)
  352.     char2octal[c] = "\\" sprintf("%03o",i)
  353.     }
  354.     for (i = 32; i < 127; i++) {
  355.     c = sprintf("%c",i)
  356.     char2octal[c]  = UncTable[c] = sprintf("%c",i)
  357.     }
  358.     UncTable[c = sprintf("%c",127)] = "^?"
  359.     char2octal[c] = "\\0177"
  360.     for (i = 128; i < 256; i++) {
  361.     UncTable[c = sprintf("%c",i)] = "\\" sprintf("%03o",i)
  362.     char2octal[c] = "\\" sprintf("%03o",i)
  363.     }
  364. }
  365.  
  366. ### End UnControl routines
  367. ### Begin set library
  368.  
  369. function Intersection(A,B,Inter,  Elem,Count) {
  370.     for (Elem in A)
  371.     if (Elem in B) {
  372.         Inter[Elem]
  373.         Count++
  374.     }
  375.     return Count
  376. }
  377.  
  378. function Union(A,B,Both,  Elem) {
  379.     for (Elem in A)
  380.     Both[Elem]
  381.     for (Elem in B)
  382.     Both[Elem]
  383. }
  384.  
  385. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  386. function SubtractSet(Minuend,Subtrahend,  Elem) {
  387.     for (Elem in Subtrahend)
  388.     delete Minuend[Elem]
  389. }
  390.  
  391. function CopySet(From,To,  Elem) {
  392.     for (Elem in From)
  393.     To[Elem]
  394. }
  395.  
  396. # Returns 1 if Set is empty, 0 if not.
  397. function IsEmpty(Set,  i) {
  398.     for (i in Set)
  399.     return 0
  400.     return 1
  401. }
  402.  
  403. # MakeSet: make a set from a list.
  404. # An index with the name of each element of the list
  405. # is created in the given array.
  406. # Input variables: 
  407. # Elements is a string containing the list of elements.
  408. # Sep is the character that separates the elements of the list.
  409. # Output variables:
  410. # Set is the array.
  411. # Return value: the number of elements added to the set.
  412. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  413.     Num = split(Elements,Names,Sep)
  414.     for (i = 1; i <= Num; i++)
  415.     Set[Names[i]]
  416.     return Num
  417. }
  418. # Returns the number of elements in set Set
  419. function NumElem(Set,  elem,Num) {
  420.     for (elem in Set)
  421.     Num++
  422.     return Num
  423. }
  424.  
  425. # Remove all elements from Set
  426. function DeleteAll(Set,  i) {
  427.     for (i in Set)
  428.     delete Set[i]
  429. }
  430.  
  431. ### End set library
  432. ### Start of ProcArgs library
  433. # @(#) ProcArgs 1.11 96/12/08
  434. # 92/02/29 john h. dubois iii (john@armory.com)
  435. # 93/07/18 Added "#" arg type
  436. # 93/09/26 Do not count -h against MinArgs
  437. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  438. #          Removed meaning of "+" or "-" by itself.
  439. # 94/03/08 Added & option and *()< option types.
  440. # 94/04/02 Added NoRCopt to Opts()
  441. # 94/06/11 Mark numeric variables as such.
  442. # 94/07/08 Opts(): Do not require any args if h option is given.
  443. # 95/01/22 Record options given more than once.  Record option num in argv.
  444. # 95/06/08 Added ExclusiveOptions().
  445. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  446. #          Expand $VARNAME at the start of its filenames.
  447. #          Let varname=0 and -option- turn off an option.
  448. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  449. #          of the vars should be searched for in the environment.
  450. #          Check for duplicate rcfiles.
  451. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  452. #          now return various negatives values on error, not just -1, and
  453. #          Opts() may set Err to various positive values, not just 1.
  454. #          Added AllowUnrecOpt.
  455. # 96/05/23 Check type given for & option
  456. # 96/06/15 Re-port to awk
  457. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  458. #          used by other functions.
  459. # 96/10/15 Added OptChars
  460. # 96/11/01 Added exOpts arg to Opts()
  461. # 96/11/16 Added ; type
  462. # 96/12/08 Added Opt2Set() & Opt2Sets()
  463. # 96/12/27 Added CmdLineOpt()
  464.  
  465. # optlist is a string which contains all of the possible command line options.
  466. # A character followed by certain characters indicates that the option takes
  467. # an argument, with type as follows:
  468. # :    String argument
  469. # ;    Non-empty string argument
  470. # *    Floating point argument
  471. # (    Non-negative floating point argument
  472. # )    Positive floating point argument
  473. # #    Integer argument
  474. # <    Non-negative integer argument
  475. # >    Positive integer argument
  476. # The only difference the type of argument makes is in the runtime argument
  477. # error checking that is done.
  478.  
  479. # The & option is a special case used to get numeric options without the
  480. # user having to give an option character.  It is shorthand for [-+.0-9].
  481. # If & is included in optlist and an option string that begins with one of
  482. # these characters is seen, the value given to "&" will include the first
  483. # char of the option.  & must be followed by a type character other than ":"
  484. # or ";".
  485. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  486.  
  487. # Strings in argv[] which begin with "-" or "+" are taken to be
  488. # strings of options, except that a string which consists solely of "-"
  489. # or "+" is taken to be a non-option string; like other non-option strings,
  490. # it stops the scanning of argv and is left in argv[].
  491. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  492. # If an option takes an argument, the argument may either immediately
  493. # follow it or be given separately.
  494. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  495. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  496. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  497. # this feature had a flaw that caused problems in some cases.  See the OptChars
  498. # parameter to explicitly set the option-specifier characters.
  499.  
  500. # If an option that does not take an argument is given,
  501. # an index with its name is created in Options and its value is set to the
  502. # number of times it occurs in argv[].
  503.  
  504. # If an option that does take an argument is given, an index with its name is
  505. # created in Options and its value is set to the value of the argument given
  506. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  507. # If an option that takes an argument is given more than once,
  508. # Options[option-name,"count"] is incremented, and the value is assigned to
  509. # the index (option-name,instance) where instance is 2 for the second occurance
  510. # of the option, etc.
  511. # In other words, the first time an option with a value is encountered, the
  512. # value is assigned to an index consisting only of its name; for any further
  513. # occurances of the option, the value index has an extra (count) dimension.
  514.  
  515. # The sequence number for each option found in argv[] is stored in
  516. # Options[option-name,"num",instance], where instance is 1 for the first
  517. # occurance of the option, etc.  The sequence number starts at 1 and is
  518. # incremented for each option, both those that have a value and those that
  519. # do not.  Options set from a config file have a value of 0 assigned to this.
  520.  
  521. # Options and their arguments are deleted from argv.
  522. # Note that this means that there may be gaps left in the indices of argv[].
  523. # If compress is nonzero, argv[] is packed by moving its elements so that
  524. # they have contiguous integer indices starting with 0.
  525. # Option processing will stop with the first unrecognized option, just as
  526. # though -- was given except that unlike -- the unrecognized option will not be
  527. # removed from ARGV[].  Normally, an error value is returned in this case.
  528. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  529. # be found, so the number of remaining arguments is returned instead.
  530. # If OptChars is not a null string, it is the set of characters that indicate
  531. # that an argument is an option string if the string begins with one of the
  532. # characters.  A string consisting solely of two of the same option-indicator
  533. # characters stops the scanning of argv[].  The default is "-+".
  534. # argv[0] is not examined.
  535. # The number of arguments left in argc is returned.
  536. # If an error occurs, the global string OptErr is set to an error message
  537. # and a negative value is returned.
  538. # Current error values:
  539. # -1: option that required an argument did not get it.
  540. # -2: argument of incorrect type supplied for an option.
  541. # -3: unrecognized (invalid) option.
  542. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  543. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  544. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  545. {
  546. # ArgNum is the index of the argument being processed.
  547. # ArgsLeft is the number of arguments left in argv.
  548. # Arg is the argument being processed.
  549. # ArgLen is the length of the argument being processed.
  550. # ArgInd is the position of the character in Arg being processed.
  551. # Option is the character in Arg being processed.
  552. # Pos is the position in OptList of the option being processed.
  553. # NumOpt is true if a numeric option may be given.
  554.     ArgsLeft = argc
  555.     NumOpt = index(OptList,"&")
  556.     OptionNum = 0
  557.     if (OptChars == "")
  558.     OptChars = "-+"
  559.     while (OptChars != "") {
  560.     c = substr(OptChars,1,1)
  561.     OptChars = substr(OptChars,2)
  562.     OptCharSet[c]
  563.     OptTerm[c c]
  564.     }
  565.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  566.     Arg = argv[ArgNum]
  567.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  568.         break    # Not an option; quit
  569.     if (Arg in OptTerm) {
  570.         delete argv[ArgNum]
  571.         ArgsLeft--
  572.         break
  573.     }
  574.     ArgLen = length(Arg)
  575.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  576.         Option = substr(Arg,ArgInd,1)
  577.         if (NumOpt && Option ~ /[-+.0-9]/) {
  578.         # If this option is a numeric option, make its flag be & and
  579.         # its option string flag position be the position of & in
  580.         # the option string.
  581.         Option = "&"
  582.         Pos = NumOpt
  583.         # Prefix Arg with a char so that ArgInd will point to the
  584.         # first char of the numeric option.
  585.         Arg = "&" Arg
  586.         ArgLen++
  587.         }
  588.         # Find position of flag in option string, to get its type (if any).
  589.         # Disallow & as literal flag.
  590.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  591.         if (AllowUnrecOpt) {
  592.             Escape = 1
  593.             break
  594.         }
  595.         else {
  596.             OptErr = "Invalid option: " specGiven Option
  597.             return -3
  598.         }
  599.         }
  600.  
  601.         # Find what the value of the option will be if it takes one.
  602.         # NeedNextOpt is true if the option specifier is the last char of
  603.         # this arg, which means that if the option requires a value it is
  604.         # the next arg.
  605.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  606.         if (GotValue = ArgNum + 1 < argc)
  607.             Value = argv[ArgNum+1]
  608.         }
  609.         else {    # Value is included with option
  610.         Value = substr(Arg,ArgInd + 1)
  611.         GotValue = 1
  612.         }
  613.  
  614.         if (HadValue = AssignVal(Option,Value,Options,
  615.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  616.         specGiven)) {
  617.         if (HadValue < 0)    # error occured
  618.             return HadValue
  619.         if (HadValue == 2)
  620.             ArgInd++    # Account for the single-char value we used.
  621.         else {
  622.             if (NeedNextOpt) {    # option took next arg as value
  623.             delete argv[++ArgNum]
  624.             ArgsLeft--
  625.             }
  626.             break    # This option has been used up
  627.         }
  628.         }
  629.     }
  630.     if (Escape)
  631.         break
  632.     # Do not delete arg until after processing of it, so that if it is not
  633.     # recognized it can be left in ARGV[].
  634.     delete argv[ArgNum]
  635.     ArgsLeft--
  636.     }
  637.     if (compress != 0) {
  638.     dest = 1
  639.     src = argc - ArgsLeft + 1
  640.     for (count = ArgsLeft - 1; count; count--) {
  641.         ARGV[dest] = ARGV[src]
  642.         dest++
  643.         src++
  644.     }
  645.     }
  646.     return ArgsLeft
  647. }
  648.  
  649. # Assignment to values in Options[] occurs only in this function.
  650. # Option: Option specifier character.
  651. # Value: Value to be assigned to option, if it takes a value.
  652. # Options[]: Options array to return values in.
  653. # ArgType: Argument type specifier character.
  654. # GotValue: Whether any value is available to be assigned to this option.
  655. # Name: Name of option being processed.
  656. # OptionNum: Number of this option (starting with 1) if set in argv[],
  657. #     or 0 if it was given in a config file or in the environment.
  658. # SingleOpt: true if the value (if any) that is available for this option was
  659. #     given as part of the same command line arg as the option.  Used only for
  660. #     options from the command line.
  661. # specGiven is the option specifier character use, if any (e.g. - or +),
  662. # for use in error messages.
  663. # Global variables: OptErr
  664. # Return value: negative value on error, 0 if option did not require an
  665. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  666. # the arg.
  667. # Current error values:
  668. # -1: Option that required an argument did not get it.
  669. # -2: Value of incorrect type supplied for option.
  670. # -3: Bad type given for option &
  671. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  672. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  673.     # If option takes a value...    [
  674.     NumTypes = "*()#<>]"
  675.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  676.     OptErr = "Bad type given for & option"
  677.     return -3
  678.     }
  679.  
  680.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  681.     if (!GotValue) {
  682.         if (Name != "")
  683.         OptErr = "Variable requires a value -- " Name
  684.         else
  685.         OptErr = "option requires an argument -- " Option
  686.         return -1
  687.     }
  688.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  689.         OptErr = Err
  690.         return -2
  691.     }
  692.     # Mark this as a numeric variable; will be propogated to Options[] val.
  693.     if (ArgType != ":" && ArgType != ";")
  694.         Value += 0
  695.     if ((Instance = ++Options[Option,"count"]) > 1)
  696.         Options[Option,Instance] = Value
  697.     else
  698.         Options[Option] = Value
  699.     }
  700.     # If this is an environ or rcfile assignment & it was given a value...
  701.     else if (!OptionNum && Value != "") {
  702.     UsedValue = 1
  703.     # If the value is "0" or "-" and this is the first instance of it,
  704.     # do not set Options[Option]; this allows an assignment in an rcfile to
  705.     # turn off an option (for the simple "Option in Options" test) in such
  706.     # a way that it cannot be turned on in a later file.
  707.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  708.         Instance = 1
  709.     else
  710.         Instance = ++Options[Option]
  711.     # Save the value even though this is a flag
  712.     Options[Option,Instance] = Value
  713.     }
  714.     # If this is a command line flag and has a - following it in the same arg,
  715.     # it is being turned off.
  716.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  717.     UsedValue = 2
  718.     if (Option in Options)
  719.         Instance = ++Options[Option]
  720.     else
  721.         Instance = 1
  722.     Options[Option,Instance]
  723.     }
  724.     # If this is a flag assignment without a value, increment the count for the
  725.     # flag unless it was turned off.  The indicator for a flag being turned off
  726.     # is that the flag index has not been set in Options[] but it has an
  727.     # instance count.
  728.     else if (Option in Options || !((Option,1) in Options))
  729.     # Increment number of times this flag seen; will inc null value to 1
  730.     Instance = ++Options[Option]
  731.     Options[Option,"num",Instance] = OptionNum
  732.     return UsedValue
  733. }
  734.  
  735. # Option is the option letter
  736. # Value is the value being assigned
  737. # Name is the var name of the option, if any
  738. # ArgType is one of:
  739. # :    String argument
  740. # ;    Non-null string argument
  741. # *    Floating point argument
  742. # (    Non-negative floating point argument
  743. # )    Positive floating point argument
  744. # #    Integer argument
  745. # <    Non-negative integer argument
  746. # >    Positive integer argument
  747. # specGiven is the option specifier character use, if any (e.g. - or +),
  748. # for use in error messages.
  749. # Returns null on success, err string on error
  750. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  751.     if (ArgType == ":")
  752.     return ""
  753.     if (ArgType == ";") {
  754.     if (Value == "")
  755.         Err = "must be a non-empty string"
  756.     }
  757.     # A number begins with optional + or -, and is followed by a string of
  758.     # digits or a decimal with digits before it, after it, or both
  759.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  760.     Err = "must be a number"
  761.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  762.     Err = "may not include a fraction"
  763.     else if (ArgType ~ "[()<>]" && Value < 0)
  764.     Err = "may not be negative"
  765.     # (
  766.     else if (ArgType ~ "[)>]" && Value == 0)
  767.     Err = "must be a positive number"
  768.     if (Err != "") {
  769.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  770.     if (Name != "")
  771.         return ErrStr "variable " substr(Name,1,1) " " Err
  772.     else {
  773.         if (Option == "&")
  774.         Option = Value
  775.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  776.     }
  777.     }
  778.     else
  779.     return ""
  780. }
  781.  
  782. # Note: only the above functions are needed by ProcArgs.
  783. # The rest of these functions call ProcArgs() and also do other
  784. # option-processing stuff.
  785.  
  786. # Opts: Process command line arguments.
  787. # Opts processes command line arguments using ProcArgs()
  788. # and checks for errors.  If an error occurs, a message is printed
  789. # and the program is exited.
  790. #
  791. # Input variables:
  792. # Name is the name of the program, for error messages.
  793. # Usage is a usage message, for error messages.
  794. # OptList the option description string, as used by ProcArgs().
  795. # MinArgs is the minimum number of non-option arguments that this
  796. # program should have, non including ARGV[0] and +h.
  797. # If the program does not require any non-option arguments,
  798. # MinArgs should be omitted or given as 0.
  799. # rcFiles, if given, is a colon-seprated list of filenames to read for
  800. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  801. # by the value of the environment variable HOME.  If a filename begins with
  802. # $, the part from the character after the $ up until (but not including)
  803. # the first character not in [a-zA-Z0-9_] will be searched for in the
  804. # environment; if found its value will be substituted, if not the filename will
  805. # be discarded.
  806. # rcfiles are read in the order given.
  807. # Values given in them will not override values given on the command line,
  808. # and values given in later files will not override those set in earlier
  809. # files, because AssignVal() will store each with a different instance index.
  810. # The first instance of each variable, either on the command line or in an
  811. # rcfile, will be stored with no instance index, and this is the value
  812. # normally used by programs that call this function.
  813. # VarNames is a comma-separated list of variable names to map to options,
  814. # in the same order as the options are given in OptList.
  815. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  816. # searched for in the environment.  If set to -1, all values will be searched
  817. # for in the environment.  Values given in the environment will override
  818. # those given in the rcfiles but not those given on the command line.
  819. # NoRCopt, if given, is an additional letter option that if given on the
  820. # command line prevents the rcfiles from being read.
  821. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  822. # ExclusiveOptions() for a description of exOpts.
  823. # Special options:
  824. # If x is made an option and is given, some debugging info is output.
  825. # h is assumed to be the help option.
  826.  
  827. # Global variables:
  828. # The command line arguments are taken from ARGV[].
  829. # The arguments that are option specifiers and values are removed from
  830. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  831. # The number of elements in ARGV[] should be in ARGC.
  832. # After processing, ARGC is set to the number of elements left in ARGV[].
  833. # The option values are put in Options[].
  834. # On error, Err is set to a positive integer value so it can be checked for in
  835. # an END block.
  836. # Return value: The number of elements left in ARGV is returned.
  837. # Must keep OptErr global since it may be set by InitOpts().
  838. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  839. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  840.     if (MinArgs == "")
  841.     MinArgs = 0
  842.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  843.     optChars)
  844.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  845.     if (ArgsLeft >= 0) {
  846.         OptErr = "Not enough arguments"
  847.         Err = 4
  848.     }
  849.     else
  850.         Err = -ArgsLeft
  851.     printf "%s: %s.\nUse -h for help.\n%s\n",
  852.     Name,OptErr,Usage > "/dev/stderr"
  853.     exit 1
  854.     }
  855.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  856.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  857.     {
  858.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  859.     Err = -e
  860.     exit 1
  861.     }
  862.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  863.     {
  864.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  865.     Err = 1
  866.     exit 1
  867.     }
  868.     return ArgsLeft
  869. }
  870.  
  871. # ReadConfFile(): Read a file containing var/value assignments, in the form
  872. # <variable-name><assignment-char><value>.
  873. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  874. # line and whitespace between the variable name and the assignment character) 
  875. # is stripped.  Lines that do not contain an assignment operator or which
  876. # contain a null variable name are ignored, other than possibly being noted in
  877. # the return value.  If more than one assignment is made to a variable, the
  878. # first assignment is used.
  879. # Input variables:
  880. # File is the file to read.
  881. # Comment is the line-comment character.  If it is found as the first non-
  882. #     whitespace character on a line, the line is ignored.
  883. # Assign is the assignment string.  The first instance of Assign on a line
  884. #     separates the variable name from its value.
  885. # If StripWhite is true, whitespace around the value (whitespace between the
  886. #     assignment char and trailing whitespace on the line) is stripped.
  887. # VarPat is a pattern that variable names must match.  
  888. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  889. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  890. #     a line; no assignment operator is needed.  These variables are set in
  891. #     the output array with a null value.  Lines containing nothing but
  892. #     whitespace are still ignored.
  893. # Output variables:
  894. # Values[] contains the assignments, with the indexes being the variable names
  895. #     and the values being the assigned values.
  896. # Lines[] contains the line number that each variable occured on.  A flag set
  897. #     is record by giving it an index in Lines[] but not in Values[].
  898. # Return value:
  899. # If any errors occur, a string consisting of descriptions of the errors
  900. # separated by newlines is returned.  In no case will the string start with a
  901. # numeric value.  If no errors occur,  the number of lines read is returned.
  902. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  903. FlagsOK,
  904. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  905.     if (Comment != "")
  906.     Comment = "^" Comment
  907.     AssignLen = length(Assign)
  908.     if (VarPat == "")
  909.     VarPat = "."    # null varname not allowed
  910.     while ((Status = (getline Line < File)) == 1) {
  911.     LineNum++
  912.     sub("^[ \t]+","",Line)
  913.     if (Line == "")        # blank line
  914.         continue
  915.     if (Comment != "" && Line ~ Comment)
  916.         continue
  917.     if (Pos = index(Line,Assign)) {
  918.         Var = substr(Line,1,Pos-1)
  919.         Val = substr(Line,Pos+AssignLen)
  920.         if (StripWhite) {
  921.         sub("^[ \t]+","",Val)
  922.         sub("[ \t]+$","",Val)
  923.         }
  924.     }
  925.     else {
  926.         Var = Line    # If no value, var is entire line
  927.         Val = ""
  928.     }
  929.     if (!FlagsOK && Val == "") {
  930.         Errs = Errs \
  931.         sprintf("\nBad assignment on line %d of file %s: %s",
  932.         LineNum,File,Line)
  933.         continue
  934.     }
  935.     sub("[ \t]+$","",Var)
  936.     if (Var !~ VarPat) {
  937.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  938.         LineNum,File,Var)
  939.         continue
  940.     }
  941.     if (!(Var in Lines)) {
  942.         Lines[Var] = LineNum
  943.         if (Pos)
  944.         Values[Var] = Val
  945.     }
  946.     }
  947.     if (Status)
  948.     Errs = Errs "\nCould not read file " File
  949.     close(File)
  950.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  951. }
  952.  
  953. # Variables:
  954. # Data is stored in Options[].
  955. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  956. # Global vars:
  957. # Sets OptErr.  Uses ENVIRON[].
  958. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  959. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  960. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  961. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  962.     split("",filesRead,"")    # make awk know this is an array
  963.     NumVars = split(VarNames,Vars,",")
  964.     TypesInd = Ret = 0
  965.     if (EnvSearch == -1)
  966.     EnvSearch = NumVars
  967.     for (i = 1; i <= NumVars; i++) {
  968.     Var = Vars[i]
  969.     CharOpt = substr(OptList,++TypesInd,1)
  970.     if (CharOpt ~ "^[:;*()#<>&]$")
  971.         CharOpt = substr(OptList,++TypesInd,1)
  972.     Map[Var] = CharOpt
  973.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  974.     # Do not overwrite entries from environment
  975.     if (i <= EnvSearch && Var in ENVIRON &&
  976.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  977.         return Err
  978.     }
  979.  
  980.     numrcFiles = split(rcFiles,fNames,":")
  981.     for (i = 1; i <= numrcFiles; i++) {
  982.     rcFile = fNames[i]
  983.     if (rcFile ~ "^~/")
  984.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  985.     else if (rcFile ~ /^\$/) {
  986.         rcFile = substr(rcFile,2)
  987.         match(rcFile,"^[a-zA-Z0-9_]*")
  988.         envvar = substr(rcFile,1,RLENGTH)
  989.         if (envvar in ENVIRON)
  990.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  991.         else
  992.         continue
  993.     }
  994.     if (rcFile in filesRead)
  995.         continue
  996.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  997.     # may be the same
  998.     filesRead[rcFile]
  999.     if ("x" in Options)
  1000.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1001.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1002.     if (retStr > 0)
  1003.         READ_RCFILE = 1
  1004.     else if (ret != "") {
  1005.         OptErr = retStr
  1006.         Ret = -1
  1007.     }
  1008.     for (Var in Lines)
  1009.         if (Var in Map) {
  1010.         if ((Err = AssignVal(Map[Var],
  1011.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1012.         Var in Values,Var,0)) < 0)
  1013.             return Err
  1014.         }
  1015.         else {
  1016.         OptErr = sprintf(\
  1017.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1018.         Lines[Var],rcFile)
  1019.         Ret = -1
  1020.         }
  1021.     }
  1022.  
  1023.     if ("x" in Options)
  1024.     for (Var in Map)
  1025.         if (Map[Var] in Options)
  1026.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1027.         "/dev/stderr"
  1028.         else
  1029.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1030.     return Ret
  1031. }
  1032.  
  1033. # OptSets is a semicolon-separated list of sets of option sets.
  1034. # Within a list of option sets, the option sets are separated by commas.  For
  1035. # each set of sets, if any option in one of the sets is in Options[] AND any
  1036. # option in one of the other sets is in Options[], an error string is returned.
  1037. # If no conflicts are found, nothing is returned.
  1038. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1039. # the exclusions presented by the first set of sets (ab,def,g) if:
  1040. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1041. # (a or b is in Options[]) AND (g is in Options) OR
  1042. # (d, e, or f is in Options[]) AND (g is in Options)
  1043. # An error will be returned due to the exclusions presented by the second set
  1044. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1045. # todo: make options given on command line unset options given in config file
  1046. # todo: that they conflict with.
  1047. function ExclusiveOptions(OptSets,Options,
  1048. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1049. SetNum,OSetNum) {
  1050.     NumSetSets = split(OptSets,SetSets,";")
  1051.     # For each set of sets...
  1052.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1053.     # NumSets is the number of sets in this set of sets.
  1054.     NumSets = split(SetSets[SetSet],Sets,",")
  1055.     # For each set in a set of sets except the last...
  1056.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1057.         s1 = Sets[SetNum]
  1058.         L1 = length(s1)
  1059.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1060.         # If any of the options in this set was given, check whether
  1061.         # any of the options in the other sets was given.  Only check
  1062.         # later sets since earlier sets will have already been checked
  1063.         # against this set.
  1064.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1065.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1066.             s2 = Sets[OSetNum]
  1067.             L2 = length(s2)
  1068.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1069.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1070.                 ErrStr = ErrStr "\n"\
  1071.                 sprintf("Cannot give both %s and %s options.",
  1072.                 c1,c2)
  1073.             }
  1074.     }
  1075.     }
  1076.     if (ErrStr != "")
  1077.     return substr(ErrStr,2)
  1078.     return ""
  1079. }
  1080.  
  1081. # The value of each instance of option Opt that occurs in Options[] is made an
  1082. # index of Set[].
  1083. # The return value is the number of instances of Opt in Options.
  1084. function Opt2Set(Options,Opt,Set,  count) {
  1085.     if (!(Opt in Options))
  1086.     return 0
  1087.     Set[Options[Opt]]
  1088.     count = Options[Opt,"count"]
  1089.     for (; count > 1; count--)
  1090.     Set[Options[Opt,count]]
  1091.     return count
  1092. }
  1093.  
  1094. # The value of each instance of option Opt that occurs in Options[] that
  1095. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1096. # Other values are made indexes of Set[].
  1097. # The return value is the number of instances of Opt in Options.
  1098. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1099.     ret = Opt2Set(Options,Opt,aSet)
  1100.     for (value in aSet)
  1101.     if (substr(value,1,1) == "!")
  1102.         nSet[substr(value,2)]
  1103.     else
  1104.         Set[value]
  1105.     return ret
  1106. }
  1107.  
  1108. # Returns true if option Opt was given on the command line.
  1109. function CmdLineOpt(Options,Opt,  i) {
  1110.     for (i = 1; (Opt,"num",i) in Options; i++)
  1111.     if (Options[Opt,"num",i] != 0)
  1112.         return 1
  1113.     return 0
  1114. }
  1115. ### End of ProcArgs library
  1116.